"""
    名称：
        英文名称：Module.Board
        中文名称：辅助控制模块

    定义：
        【类】AssistedClass       棋盘类
"""

# 导入相关模块
import tkinter                                  # GUI模块
import tkinter.messagebox                       # GUI-提示框模块
import socket                                   # 网络链接模块
import json                                     # JSON模块
import os                                       # 操作系统相关（NT）模块
import threading                                # 线程模块
import time                                     # 时间模块
import Module.SocketThread as STC               # Socket线程类模块
from Module import AppState                     # 自定义：APP状态（枚举）
from Module import AppLocalType                 # 自定义：本机类型（枚举）
from Module.Functions import Get_Broadcast_IP   # 获取局域网广播地址


class AssistedClass(tkinter.Frame):
    """
        辅助信息类，负责控制类按钮等

        继承自tkinter.Frame类

        方法：
            __init__：初始化
            __create_assisted：绘制辅助组件
            __onStartBlack_click：执黑开始
            __onStartWhite_click：执白开始
            __onEnd_click：结束对弈（内部按钮触发事件）
            __onFindServer_click：寻找执黑主机
            __black_list_clear：清空执黑主机列表
            __black_list_update：更新执黑主机列表控件
            __onConnectServer_click：连接一个执黑主机
            __count_down：对弈倒计时
            Show_ErrorMsg：显示错误提示
            Show_InfoMsg：显示正常提示
            Send_Point：发送一个坐标到对方
            End：结束对弈（外部调用）
            Show_Nickname：设置当前用户昵称
            Black_List_Add：添加一个新的执黑主机
            Black_List_Del：删除一个执黑主机
            Init_Assisted：初始化对弈信息
            Switch_Identity：切换对弈身份显示落子状态
    """

    def __init__(self, master=None):
        """
            Frame初始化

            参数：
                master=None：隶属对象，当前为主窗口
        """

        """
        利用继承tkinter库的Frame类实现棋盘（区域）组件绘制
        """

        # 调用父类初始化方法进行对象初始化
        tkinter.Frame.__init__(self, master, width=300, height=801)

        """
        定义类的属性变量
        """

        # 存储隶属对象（主窗体）
        self.__APP = master
        # 初始化线程
        self.__Thread = None
        # 执黑主机列表，默认为空
        self.__black_list = []
        # 倒计时CountDown线程
        self.__Thread_CD = None

        """
        调用内部方法，实现辅助信息组件的创建和绘制
        """

        # 创建辅助Frame
        self.__create_assisted()
        # 绘制辅助Frame
        self.pack_propagate(tkinter.FALSE)  # 取消父组件几何大小与子组件控制
        self.place(x=802, y=0)

    def __create_assisted(self):
        """
            绘制辅助信息
        """

        """
        创建辅助对象组件（包含不显示的）
        """

        # 欢迎信息
        nk_font = tkinter.font.Font(family='微软雅黑',
                                    size=14, weight=tkinter.font.BOLD)
        self.__lab_nk = tkinter.Label(self, text='欢迎', font=nk_font)
        self.__lab_nk.place(relx=0.5, y=10, relwidth=1,
                            height=50, anchor=tkinter.N)
        # 本机IP
        self.__lab_IP = tkinter.Label(
            self, text="本机IP:%s" % self.__APP.LocalIP)
        self.__lab_IP.place(x=300, y=801, relwidth=1,
                            height=50, anchor=tkinter.SE)
        # 执黑开始 （绑定__onStartBlack_click方法）
        self.__bt_start_black = tkinter.Button(
            self, text="以执黑开始", command=self.__onStartBlack_click)
        self.__bt_start_black.place(
            relx=0.25, y=90, relwidth=0.3, height=40, anchor=tkinter.CENTER)
        # 执白开始 （绑定__onStartWhite_click方法）
        self.__bt_start_white = tkinter.Button(
            self, text="以执白开始", command=self.__onStartWhite_click)
        self.__bt_start_white.place(
            relx=0.75, y=90, relwidth=0.3, height=40, anchor=tkinter.CENTER)
        """初始不显示"""
        # 结束 （绑定__onEnd_click方法）
        self.__bt_end = tkinter.Button(
            self, text="结束对弈", command=self.__onEnd_click)
        # 寻找主机 （绑定__onFindServer_click方法）
        self.__bt_find_server = tkinter.Button(
            self, text="寻找执黑主机", command=self.__onFindServer_click)
        # 当前执黑主机列表
        self.__lstb_server = tkinter.Listbox(self)
        # 连接执黑主机 （绑定__onConnectServer_click方法）
        self.__bt_connect_server = tkinter.Button(
            self, text="连接主机", command=self.__onConnectServer_click)
        # 执黑主机列表title提示语
        self.__lab_lst_title = tkinter.Label(self, text='请选择要连接的主机：')

        """
            对弈双方的信息所需资源
        """

        # 定义棋子对象所需图片路径
        fpblack = os.getcwd() + (r'\StaticResources\Piece_hei.png')
        fpwhite = os.getcwd() + (r'\StaticResources\Piece_bai.png')
        fpplay = os.getcwd() + (r'\StaticResources\PlayNow.png')

        """
        执黑方信息
        """

        # 执黑信息Frame 初始不显示
        self.__black_info = tkinter.Frame(self, bg='white')
        # self.__black_info.place(
        #     relx=0.5, y=100, width=270, height=61, anchor=tkinter.CENTER)
        # 执黑棋子标识
        img = tkinter.PhotoImage(file=fpblack)
        self.__bi_chessman_black = tkinter.Label(
            self.__black_info, image=img, bd=0,
            fg='white', text='60', compound=tkinter.CENTER)
        self.__bi_chessman_black.image = img  # 设置背景
        self.__bi_chessman_black.place(x=31, y=31, width=41,
                                       height=41, anchor=tkinter.CENTER)
        # 执黑昵称
        self.__bi_nickname_black = tkinter.Label(
            self.__black_info, bg='white', text='Nickname')
        self.__bi_nickname_black.place(x=61, y=10)
        # 执黑IP
        self.__bi_ip_black = tkinter.Label(
            self.__black_info, bg='white', text='IP：0.0.0.0', fg='#999999')
        self.__bi_ip_black.place(x=61, y=30)
        # 执黑落子状态
        img = tkinter.PhotoImage(file=fpplay)
        self.__bi_play_black = tkinter.Label(
            self.__black_info, image=img, bd=0)
        self.__bi_play_black.image = img  # 设置背景
        self.__bi_play_black.place(x=260, y=10, width=11,
                                   height=11, anchor=tkinter.NE)

        """
        执白信息
        """

        # 执白信息Frame 初始不显示
        self.__white_info = tkinter.Frame(self, bg='white')
        # self.__white_info.place(
        #     relx=0.5, y=170, width=270, height=61, anchor=tkinter.CENTER)
        # 执白棋子标识
        img = tkinter.PhotoImage(file=fpwhite)
        self.__bi_chessman_white = tkinter.Label(
            self.__white_info, image=img, bd=0, fg='red', text='60', compound=tkinter.CENTER)
        self.__bi_chessman_white.image = img  # 设置背景
        self.__bi_chessman_white.place(x=31, y=31, width=41,
                                       height=41, anchor=tkinter.CENTER)
        # 执白昵称
        self.__bi_nickname_white = tkinter.Label(
            self.__white_info, bg='white', text='Nickname')
        self.__bi_nickname_white.place(x=61, y=10)
        # 执白IP
        self.__bi_ip_white = tkinter.Label(
            self.__white_info, bg='white', text='IP：0.0.0.0', fg='#999999')
        self.__bi_ip_white.place(x=61, y=30)
        # 执白落子状态
        img = tkinter.PhotoImage(file=fpplay)
        self.__bi_play_white = tkinter.Label(
            self.__white_info, image=img, bd=0)
        self.__bi_play_white.image = img  # 设置背景
        self.__bi_play_white.place(x=260, y=10, width=11,
                                   height=11, anchor=tkinter.NE)

    def __onStartBlack_click(self):
        """
            执黑开始，做主机端
        """

        """
        判断本机IP是否获取成功
        """

        if self.__APP.LocalIP is None:
            self.Show_ErrorMsg('错误', '本机IP获取错误无法继续！')
            return

        """
        显示和隐藏相关组件
        """

        # 隐藏执黑和执白开始按钮
        self.__bt_start_black.place_forget()
        self.__bt_start_white.place_forget()
        # 显示关闭按钮
        self.__bt_end.place(relx=0.5, y=700, relwidth=0.8,
                            height=40, anchor=tkinter.N)
        # 显示执黑信息
        self.__black_info.place(
            relx=0.5, y=100, width=270, height=61, anchor=tkinter.CENTER)
        # 显示执白信息
        self.__white_info.place(
            relx=0.5, y=170, width=270, height=61, anchor=tkinter.CENTER)

        """
        修改系统相关状态符和初始化数据
        """

        # 修改监听线程的状态符
        self.__APP.ThreadState = True
        # 设置APP状态
        self.__APP.AppState = AppState.BlackReady
        # 设置APP本地类型
        self.__APP.LocalType = AppLocalType.Black
        # 初始化对弈对手IP、昵称、对弈状态
        self.__APP.OpponentIP = None
        self.__APP.OpponentNickname = None
        self.__APP.IsPlay = False
        # 初始化主机棋盘
        self.__APP.Board.Init_Board()
        # 初始化执白和执黑信息
        self.__bi_chessman_black["text"] = ""
        self.__bi_nickname_black["text"] = self.__APP.Nickname
        self.__bi_ip_black["text"] = "IP:%s" % self.__APP.LocalIP
        self.__bi_chessman_white["text"] = ""
        self.__bi_nickname_white["text"] = '等待对方接入...'
        self.__bi_ip_white["text"] = ""
        self.__bi_play_black.place_forget()
        self.__bi_play_white.place_forget()

        """
        创建线程对象，并启动线程
        """

        # 创建监听线程
        self.__Thread = STC.SocketThread(
            self.__APP, self.__APP.LocalIP, self.__APP.BlackPort)
        # 守护线程
        self.__Thread.setDaemon(True)
        # 启动线程
        self.__Thread.start()

    def __onStartWhite_click(self):
        """
            执白开始，做客户端
        """

        """
        判断本机IP是否获取成功
        """

        if self.__APP.LocalIP is None:
            self.Show_ErrorMsg('错误', '本机IP获取错误无法继续！')
            return

        """
        显示和隐藏相关组件
        """

        # 隐藏执黑和执白开始按钮
        self.__bt_start_black.place_forget()
        self.__bt_start_white.place_forget()
        # 显示关闭按钮
        self.__bt_end.place(relx=0.5, y=700, relwidth=0.8,
                            height=40, anchor=tkinter.N)
        # 执黑主机列表title提示语
        self.__lab_lst_title.place(x=0, y=80)
        # 主机列表
        self.__lstb_server.place(x=5, y=110, width=290, height=300)
        # 寻找主机
        self.__bt_find_server.place(
            relx=0.25, y=440, relwidth=0.4, height=40, anchor=tkinter.CENTER)
        # 连接主机
        self.__bt_connect_server.place(
            relx=0.75, y=440, relwidth=0.4, height=40, anchor=tkinter.CENTER)

        """
        修改系统相关状态符和初始化数据
        """

        # 修改监听线程的状态符
        self.__APP.ThreadState = True
        # 设置APP状态
        self.__APP.AppState = AppState.WhiteReady
        # 设置APP本地类型
        self.__APP.LocalType = AppLocalType.White
        # 初始化对弈对手IP、昵称、对弈状态
        self.__APP.OpponentIP = None
        self.__APP.OpponentNickname = None
        self.__APP.IsPlay = False
        # 初始化主机棋盘
        self.__APP.Board.Init_Board()
        # 清空执黑主机列表及（listbox）控件
        self.__black_list_clear()

        """
        创建线程对象，并启动线程
        """

        # 创建监听线程
        self.__Thread = STC.SocketThread(
            self.__APP, self.__APP.LocalIP, self.__APP.WhitePort)
        # 守护线程
        self.__Thread.setDaemon(True)
        # 启动线程
        self.__Thread.start()

    def __onEnd_click(self):
        """
            结束对弈
        """

        """
        修改系统相关状态符和初始化数据
        """

        # 修改监听线程的状态符
        self.__APP.ThreadState = False
        # 设置APP状态
        self.__APP.AppState = AppState.Init
        # 设置APP本地类型
        self.__APP.LocalType = AppLocalType.Default
        # 初始化对弈对手IP、昵称、对弈状态
        self.__APP.OpponentIP = None
        self.__APP.OpponentNickname = None
        self.__APP.IsPlay = False
        # 初始化计时器线程
        self.__Thread_CD = None

        """
        关闭线程
        """

        # 关闭线程中的socket链接，调用线程对象的方法，关闭线程中的socket对象
        if self.__Thread and self.__Thread.is_alive():
            # 调用线程对象关闭方法，关闭线程相关Socket对象
            self.__Thread.Close()

        """
        处理隐藏和显示组件
        """

        # 执黑开始 显示
        self.__bt_start_black.place(
            relx=0.25, y=90, relwidth=0.3, height=40, anchor=tkinter.CENTER)
        # 执白开始 显示
        self.__bt_start_white.place(
            relx=0.75, y=90, relwidth=0.3, height=40, anchor=tkinter.CENTER)
        self.__bt_end.pack_forget()
        # 当前执黑主机列表 隐藏
        self.__lstb_server.place_forget()
        # 寻找主机 隐藏
        self.__bt_find_server.place_forget()
        # 连接执黑主机 隐藏
        self.__bt_connect_server.place_forget()
        # 执黑主机列表title提示语 隐藏
        self.__lab_lst_title.place_forget()
        # 结束 隐藏
        self.__bt_end.place_forget()

        """
        隐藏对弈信息框架Frame
        """

        # 执黑信息
        self.__black_info.place_forget()
        # 执白信息
        self.__white_info.place_forget()

    def __onFindServer_click(self):
        """
            广播找执黑主机
        """

        """
        创建命令JSON格式，命令格式请参考 SocketThread 模块中的 命令处理函数
        """

        # 创建命令
        cmd = dict(cmd='Search', args=None)
        # 生成json
        jsoncmd = json.dumps(cmd)

        """
        创建广播UDP协议的Socket对象，并发送命令，发送完毕后关闭发送socket对象
        """

        # 创建广播socket对象，使用UDP协议
        udp_broadcast = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 配置socket对象，广播类型
        udp_broadcast.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        # 获取本局域网内的广播地址
        broadcast_ip = Get_Broadcast_IP()
        # 向局域网内所有主机的BlackPort端口发送广播信息（Search命令）等待回复
        udp_broadcast.sendto(jsoncmd.encode('utf-8'),
                             (broadcast_ip, self.__APP.BlackPort))
        # 发送完毕后直接关闭
        udp_broadcast.shutdown(socket.SHUT_RDWR)
        udp_broadcast.close()

    def __black_list_clear(self):
        """
            清空执黑主机列表，并更新执黑主机列表控件内容
        """

        # 清空列表
        self.__black_list.clear()
        # 更新控件内容
        self.__black_list_update()

    def __black_list_update(self):
        """
            更新执黑主机列表控件
        """

        # 清空组件
        self.__lstb_server.delete(0, tkinter.END)
        # 根据列表重新生成
        for item in self.__black_list:
            self.__lstb_server.insert(
                tkinter.END, '%s:%s' % (item[0], item[1]))
        # 清空所有选中
        self.__lstb_server.select_clear(0, tkinter.END)

    def __onConnectServer_click(self):
        """
            连接到某台执黑主机
        """

        """
        判断选中主机数量，不等于1则弹出提醒，等于1则发起连接
        """

        if len(self.__lstb_server.curselection()) != 1:  # 选中多条或一条没选中
            self.Show_ErrorMsg('选中错误', '请选中一个要链接的主机！')
            return
        else:  # 选中一条
            """
            获取选中的索引，然后通过List数据获取主机信息，然后发起连接
            """
            # 获取主机信息（IP）
            index = self.__lstb_server.curselection()[0]
            connIP = self.__black_list[index][0]
            # 发起连接
            self.__Thread.Connect_Black(connIP)

    def __count_down(self):
        """
            对弈计数器倒计时
        """

        # 计数器
        t = self.__APP.CountDown

        # 当前对弈状态，默认等于当前APP的对弈状态
        isplay = self.__APP.IsPlay

        # 延迟计数
        sleep_i = 0

        # 循环倒计时 每1秒执行一次
        while True:
            # 监听线程退出则计数器线程退出
            if not self.__APP.ThreadState:
                self.__bi_chessman_black["text"] = ""
                self.__bi_chessman_white["text"] = ""
                break
            # 如果主机对弈状态发生变化，初始计数器
            if self.__APP.IsPlay != isplay:
                # 初始化计数器
                t = self.__APP.CountDown
                # 修改对弈状态
                isplay = self.__APP.IsPlay
                # 延迟计数器初始
                sleep_i = 0

            # 延迟计数器+1
            sleep_i = sleep_i+1

            # 判断当前对弈状态
            if isplay:  # 当前为我方落子状态 我方倒计时
                if self.__APP.LocalType == AppLocalType.Black:  # 我方执黑
                    self.__bi_chessman_black["text"] = str(t)
                    self.__bi_chessman_white["text"] = ""
                if self.__APP.LocalType == AppLocalType.White:  # 我方执白
                    self.__bi_chessman_black["text"] = ""
                    self.__bi_chessman_white["text"] = str(t)
            else:  # 当前为对方落子，对方倒计时
                if self.__APP.LocalType == AppLocalType.Black:  # 我方执黑
                    self.__bi_chessman_black["text"] = ""
                    self.__bi_chessman_white["text"] = str(t)
                if self.__APP.LocalType == AppLocalType.White:  # 我方执白
                    self.__bi_chessman_black["text"] = str(t)
                    self.__bi_chessman_white["text"] = ""
            # 当延迟计数器 =10 说明执行10次，每次0.1秒，10次=1秒
            if sleep_i == 10:
                # 计数器-1
                t = t-1
                # 延迟计数器初始
                sleep_i = 0
            # 超时提醒
            if t <= 0:
                # 结束对弈
                self.__onEnd_click()
                # 弹出提醒
                if isplay:  # 当前为我方落子状态
                    self.Show_InfoMsg("超时", "超时，结束对弈！")
                else:  # 当前为对方落子，对方倒计时
                    self.Show_InfoMsg("超时", "对方超时，结束对弈！")
            # 延迟0.1秒 通过延迟0.1秒
            time.sleep(0.1)

    def Show_ErrorMsg(self, t, msg):
        """
            错误弹窗提醒消息
        """

        tkinter.messagebox.showerror(title=t, message=msg)

    def Show_InfoMsg(self, t, msg):
        """
            正常弹窗提醒消息
        """

        tkinter.messagebox.showinfo(title=t, message=msg)

    def Send_Point(self, ip, x, y):
        """
            发送棋子信息到对方
        """

        if self.__APP.LocalType != AppLocalType.Default:
            # 调用 线程 对象的发送方法
            self.__Thread.Send_Point(ip, x, y)

    def End(self):
        """
            结束当前对弈
        """

        # 调用结束当前对弈（内部）方法
        self.__onEnd_click()

    def Show_Nickname(self):
        """
            设置当前用户昵称
        """

        self.__lab_nk["text"] = "欢迎 %s 进入系统" % self.__APP.Nickname

    def Black_List_Add(self, s):
        """
            向执黑主机列表添加一个执黑主机
        """

        # 通过判断IP筛选已经在列表中的执黑主机
        # s = ['192.168.0.119', ['Nickname', 'Else']]
        if len(list(filter(lambda x: x[0] == s[0], self.__black_list))) > 0:
            return
        # 添加列表
        self.__black_list.append(s)
        # 更新组件
        self.__black_list_update()

    def Black_List_Del(self, ip):
        """
            从执黑主机列表删除一个执黑主机
        """

        i = 0
        for l in self.__black_list:
            if l[0] == ip:
                self.__black_list.pop(i)
                break
            i = i+1
        # 更新组件
        self.__black_list_update()

    def Init_Assisted(self):
        """
            初始化对弈信息
        """

        """
        显示基本信息框架Frame
        """

        # 显示执黑信息
        self.__black_info.place(
            relx=0.5, y=100, width=270, height=61, anchor=tkinter.CENTER)
        # 显示执白信息
        self.__white_info.place(
            relx=0.5, y=170, width=270, height=61, anchor=tkinter.CENTER)

        """
        根据本机状态，处理对弈双方信息显示
        """

        # 根据本机状态初始化对弈双方信息
        if self.__APP.LocalType == AppLocalType.Black:  # 执黑
            """
            初始化对弈信息
            """
            # 初始化执白和执黑信息
            self.__bi_chessman_black["text"] = ""
            self.__bi_nickname_black["text"] = self.__APP.Nickname
            self.__bi_ip_black["text"] = "IP:%s" % self.__APP.LocalIP
            self.__bi_chessman_white["text"] = ""
            self.__bi_nickname_white["text"] = self.__APP.OpponentNickname
            self.__bi_ip_white["text"] = "IP:%s" % self.__APP.OpponentIP
        elif self.__APP.LocalType == AppLocalType.White:  # 执白
            """
            初始化对弈信息
            """
            # 初始化执白和执黑信息
            self.__bi_chessman_black["text"] = ""
            self.__bi_nickname_black["text"] = self.__APP.OpponentNickname
            self.__bi_ip_black["text"] = "IP:%s" % self.__APP.OpponentIP
            self.__bi_chessman_white["text"] = ""
            self.__bi_nickname_white["text"] = self.__APP.Nickname
            self.__bi_ip_white["text"] = "IP:%s" % self.__APP.LocalIP

            """
            隐藏搜索主机相关控件
            """
            # 执黑主机列表title提示语
            self.__lab_lst_title.place_forget()
            # 主机列表
            self.__lstb_server.place_forget()
            # 寻找主机
            self.__bt_find_server.place_forget()
            # 连接主机
            self.__bt_connect_server.place_forget()

        """
        初始化默认执黑先行，修改执黑落子状态为显示
        """

        self.__bi_play_white.place_forget()
        self.__bi_play_black.place(x=260, y=10, width=11,
                                   height=11, anchor=tkinter.NE)

    def Switch_Identity(self):
        """
            切换落子身份，当前为落子or等待

            开启倒计时线程
        """

        if self.__Thread_CD is None:
            self.__Thread_CD = threading.Thread(target=self.__count_down)
            self.__Thread_CD.start()

        """
        处理执黑的身份切换
        """

        if self.__APP.LocalType == AppLocalType.Black:
            if(self.__APP.IsPlay):  # 当前落子状态
                self.__bi_play_white.place_forget()
                self.__bi_play_black.place(x=260, y=10, width=11,
                                           height=11, anchor=tkinter.NE)
            else:  # 非落子状态
                self.__bi_play_white.place(x=260, y=10, width=11,
                                           height=11, anchor=tkinter.NE)
                self.__bi_play_black.place_forget()
        """
        处理执白的身份切换
        """

        if self.__APP.LocalType == AppLocalType.White:
            if(self.__APP.IsPlay):  # 当前落子状态
                self.__bi_play_white.place(x=260, y=10, width=11,
                                           height=11, anchor=tkinter.NE)
                self.__bi_play_black.place_forget()
            else:  # 非落子状态
                self.__bi_play_white.place_forget()
                self.__bi_play_black.place(x=260, y=10, width=11,
                                           height=11, anchor=tkinter.NE)
